<!--
* @Author: yx
* @Description: 下拉选择树
-->
<template>
  <div class="eve-drop-down-tree">
    <el-select
      v-model="tempValue"
      placeholder="请选择"
      :style="{ width: `${checkString(width)}` }"
      clearable
      :multiple="multiple"
      :collapse-tags="collapseTags"
      ref="eveDropDownTreeSelect"
      @clear="clear"
      @visible-change="visibleChange"
      @remove-tag="removeTag"
      :class="[
        `eve-drop-down-tree__select${random}`,
        columnCollapseTags && 'eve-drop-down-tree__column-collapse-tags',
      ]"
      :filterable="filterable"
      :filter-method="filterMethod"
    >
      <el-option
        :label="label"
        :value="tempValue"
        disabled
        class="eve-drop-down-tree__option"
        :style="{ width: checkString(width), height: checkString(height) }"
      >
        <el-scrollbar style="width: 100%; height: 100%">
          <section class="eve-drop-down-tree__content">
            <el-tree
              :data="tempData"
              :props="props"
              class="eve-drop-down-tree__item"
              :class="[
                !onlyLeaf && 'eve-drop-down-tree__is-active',
                multiple && 'eve-drop-down-tree__multiple',
              ]"
              :node-key="nodeKey"
              :highlight-current="true"
              :expand-on-click-node="false"
              :default-expand-all="defaultExpandAll"
              :filter-node-method="filterNodeMethod"
              :show-checkbox="showCheckbox"
              :default-expanded-keys="defaultExpandedKeys"
              :accordion="accordion"
              :auto-expand-parent="autoExpandParent"
              @check="check"
              @node-click="nodeClick"
              ref="tree"
              v-bind="$attrs"
            ></el-tree>
          </section>
        </el-scrollbar>
      </el-option>
      <!-- 隐藏域 让多选显示名称-->
      <div v-show="false">
        <el-option
          v-for="item in option"
          :key="item[nodeKey]"
          :value="item[nodeKey]"
          :label="item[props.label]"
          >{{ item[props.label] }}</el-option
        >
      </div>
    </el-select>

    <div
      class="eve-drop-down-tree__number"
      v-show="number"
      :style="{ left: `${numberLeft}px`, marginLeft: `${spacing}px` }"
    >
      {{ `+${number}` }}
    </div>
  </div>
</template>

<script lang='ts' >
import { defineComponent, ref, watch, nextTick } from 'vue'
export default defineComponent({
  name: 'YxDropDownTree',
  emits: [
    'clear',
    'visible-change',
    'remove-tag',
    'check',
    'node-click',
    'update:data',
    'update:modelValue',
  ],
  inheritAttrs: false,
  props: {
    //树的数据
    data: {
      type: Array,
      default: () => [],
    },

    //这就是v-model
    modelValue: {
      type: [String, <any>Array],
    },

    //select宽度
    width: {
      type: [String, Number],
      default: () => '100%',
    },
    //节点是否多选
    multiple: {
      type: Boolean,
      default: () => false,
    },
    //多选时是否将选中值按文字的形式展示(是否添加+number)--注意：这个属性设置true会覆盖columnCollapseTags属性
    collapseTags: {
      type: Boolean,
      default: () => false,
    },
    //(推荐)多选时是否将选中值按文字的形式自适应展示(是否自适应添加+number),collapseTag非自适应展示,只展示一个页签后面就跟着一个number
    columnCollapseTags: {
      type: Boolean,
      default: () => false,
    },

    //是否可搜索
    filterable: {
      type: Boolean,
      default: () => true,
    },
    //option高度
    height: {
      type: [String, Number],
      default: () => 200,
    },

    //配置选项
    props: {
      type: Object,
      default: () => ({
        children: 'children', //指定子树为节点对象的某个属性值
        label: 'label', //指定节点标签为节点对象的某个属性值(下拉菜单显示值的key)
      }),
    },

    //是否只选中和高亮叶子节点
    onlyLeaf: {
      type: Boolean,
      default: () => false,
    },

    //每个树节点用来作为唯一标识的属性，整棵树应该是唯一的(下拉菜单真实值的key)
    nodeKey: {
      type: String,
      default: () => 'id',
    },
    //是否默认展开所有节点
    defaultExpandAll: {
      type: Boolean,
      default: () => false,
    },

    //是否每次只打开一个同级树节点
    accordion: {
      type: Boolean,
      default: () => false,
    },

    //展开子节点的时候是否自动展开父节点
    autoExpandParent: {
      type: Boolean,
      default: () => true,
    },
    //树形结构数据转换设置
    convertSetting: {
      type: Object,
      default: () => {},
    },
  },

  setup(props, { emit }) {
    /*----param------*/
    const {
      modelValue,
      multiple,
      onlyLeaf,
      nodeKey,
      data,
      convertSetting,
      columnCollapseTags,
    } = props
    const tempValue = ref<string | Array<any>>(multiple ? [] : '')
    const eveDropDownTreeSelect = ref<any>(null)
    const tree = ref<any>(null)
    const label = ref('') //被选中节点的label值
    const tempData = ref<Array<any>>([]) //树的数据(内部处理逻辑用)
    const showCheckbox = multiple //节点是否可被选择--多选默认可选择
    const defaultExpandedKeys = ref<Array<any>>([]) //默认展开的节点的 key 的数组
    let id = ref<string | null>('') //被选中节点的id或者唯一值
    const option = ref<Array<any>>([]) //多选时选中的lable和key组合的数组
    const tempConvertSetting = ref({
      id: 'id', //节点的唯一标识键值
      pid: 'pid', //节点的父id键值
      topmostPid: -1, //最顶层数据的pid,当前值必须设置正确，否则可能转换不成功。
      convert: false, //是否开启普通数据转换为树结构数据
    }) //树形结构数据转换设置(内部处理逻辑用)

    const number = ref<number>(0) // 未在select中显示的tags转换成个数
    const numberLeft = ref<number>(0) // 数字的偏移量
    const spacing = ref<number>(6) // 相邻两个页签的间距
    const tagsHeight = ref<number>(0) // 页签列表的高度
    const tagsWidth = ref<number>(0) // 页签列表的宽度--只修改一次

    /*----methods------*/

    /**@description 判断是否是字符串
     * @author yx
     * @param  {String}  str 高度、宽度、left等类型的值
     */

    const checkString = (str: string | number) => {
      return typeof str === 'string' ? str : `${str}px`
    }
    /**@description  获取随机数
     * @author yx
     * @param  {Number}  num 位数
     */
    const getGenerateMixed = (num: number) => {
      const chars = [
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
      ]
      let res = ''
      for (let i = 0; i < num; i++) {
        const id = Math.ceil(Math.random() * 35)
        res += chars[id]
      }
      return res
    }

    /**@description  清空选择的数据
     * @author yx
     */

    const clear = () => {
      tempValue.value = '' //清空真实值
      label.value = '' //清空select框里的label值
      id.value = null
      setCurrentKey(null)
      setCheckedKeys([])
      defaultExpandedKeys.value = []
      emit('clear')
    }

    /** @description  下拉框出现/隐藏时触发
     * @author yx
     */
    const visibleChange = (flag: boolean) => {
      emit('visible-change', flag)
    }

    /** @description  多选模式下移除tag时触发
     * @author yx
     */
    const removeTag = (key: number) => {
      tempValue.value.slice(key, 1)
      setCheckedKeys(tempValue.value)
      emit('remove-tag', key)
    }

    /**@description 过滤查找
     * @author yx
     */
    const filterMethod = (val: string) => {
      //清除左边两边的空格
      val = val.replace(/(^\s*)|(\s*$)/g, '')
      tree.value.filter(val)
    }

    /**@description 节点过滤
     * @author yx
     */
    const filterNodeMethod = (value: string, data: any) => {
      if (!value) return true
      return data[props.props.label].indexOf(value) !== -1
    }

    /**@description 当复选框被点击的时候触发
     * @author yx
     */
    const check = (data: Object, checked: Object) => {
      interface checked {
        checkedKeys: any
        checkedNodes: any
      }
      const { checkedKeys, checkedNodes } = (checked as checked) || {}
      tempValue.value = checkedKeys
      option.value = checkedNodes
      emit('check', data, checked)
    }

    /**@description  节点被点击时的回调
     * @author yx
     * @param  {Object}  data 递给 data 属性的数组中该节点所对应的对象
     * @param  {Object}  node 节点对应的 Node
     * @param  {Object}  indeterminate 节点组件本身
     */
    const nodeClick = (data: any, node: Object, indeterminate: Object) => {
      if (multiple) return
      setCurrentKey(id.value) //onlyLeaf为true时,在点击父亲爷爷的时候也只高亮叶子节点
      if (onlyLeaf && data[props.props.children]) return
      id.value = data[nodeKey]
      label.value = data[props.props.label]
      tempValue.value = data[nodeKey]
      setCurrentKey(id.value)
      eveDropDownTreeSelect.value.blur() //自动收缩
      emit('node-click', data, node, indeterminate)
    }

    /**@description  设置当前选中(高亮)的节点
     * @author yx
     * @param  {mixins}  key //节点的key一般是id
     */
    const setCurrentKey = (key: any) => {
      tree.value
        ? tree.value.setCurrentKey(key)
        : nextTick(() => {
            tree.value.setCurrentKey(key)
          })
    }
    /**@description 单选--获取当前被选中节点的 data，若没有节点被选中则返回 null
     * @author yx
     */
    const getCurrentNode = () => tree.value.getCurrentNode()

    /**@description 普通数据根据pid转树数据
     *  @param  {Array}  data 要转换的数据
     *  @param  {minix}  parentId 父id --最顶层数据的父id，有可能是空、0、-1根据后台数据格式决定,默认是-1,注意:相同值，数字的number和字符串的number不相等
     * @author yx
     */

    const convertToTree = (
      data: Array<any>,
      parentId = tempConvertSetting.value.topmostPid
    ) => {
      const itemArr = []
      const length = data.length
      const { children } = props.props
      const { pid, id } = tempConvertSetting.value
      for (let i = 0; i < length; i++) {
        const node = data[i]
        if (node[pid] === parentId) {
          const childrenData = convertToTree(data, node[id])
          if (childrenData.length > 0) {
            node[children] = childrenData
          }
          itemArr.push(node)
        }
      }
      return itemArr
    }

    /**@description 设置选中值
     * @author yx
     * @param  {Array}  keys //节点的key数组一般是id数组
     * @param  {Boolean}  leafOnly //是否仅设置叶子节点的选中状态
     */

    const setCheckedKeys = (keys: Array<any> | string, leafOnly = false) => [
      tree.value
        ? tree.value.setCheckedKeys(keys, leafOnly)
        : nextTick(() => {
            tree.value.setCheckedKeys(keys, leafOnly)
          }),
    ]

    /**@description  自适应的number
     * @author yx
     */
    let random = ref('')
    random.value = getGenerateMixed(32)
    const selfAdaption = () => {
      //dom元素赋any类型避免错误
      const documen: any = document.querySelector(
        `.eve-drop-down-tree__select${random.value} .el-select__tags`
      )
      tagsHeight.value = tagsHeight.value || documen.offsetHeight
      tagsWidth.value = tagsWidth.value || documen.offsetWidth - 30
      let itemWidth = 0 //number 宽
      let num = 0
      number.value = 0
      const arr: Array<any> = []
      documen.style.height = tagsHeight.value + 'px'
      documen.style.maxWidth = tagsWidth.value + 'px'
      const documenAll: any = document.querySelectorAll(
        `.eve-drop-down-tree__select${random.value} .el-tag`
      )
      Array.from(documenAll).some((itme: any) => {
        itemWidth += itme.offsetWidth + spacing.value
        console.log(itemWidth)
        arr.push(itme.offsetWidth + spacing.value)
        if (tagsWidth.value <= itemWidth) {
          number.value = tempValue.value.length - num
          arr.pop()
          numberLeft.value = arr.reduce((prev, curr) => {
            return prev + curr
          })
          return
        } else {
          num++
        }
      })
    }

    /**@description 多选--若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点所组成的数组
     * @author yx
     * @param  {Boolean}  leafOnly 是否只是叶子节点
     * @param  {Boolean}  includeHalfChecked 是否包含半选节点
     */

    const getCheckedNodes = (leafOnly = false, includeHalfChecked = false) =>
      tree.value.getCheckedNodes(leafOnly, includeHalfChecked)

    /*----watch------*/
    watch(
      () => modelValue,
      (newValue: any) => {
        const radio = () => {
          const value = newValue || ''
          tempValue.value = value
          defaultExpandedKeys.value = []
          value && defaultExpandedKeys.value.push(value)
          id.value = value
          setCurrentKey(value)
          nextTick(() => {
            label.value = getCurrentNode()
              ? getCurrentNode()[props.props.label]
              : value
          })
        }

        const checkbox = () => {
          const value = newValue || []
          tempValue.value = value
          defaultExpandedKeys.value = value
          setCheckedKeys(value)
          nextTick(() => {
            option.value = [] //防止重复的key
            getCheckedNodes().forEach((element: any) => {
              option.value.push({
                label: element[props.props.label],
                id: element[nodeKey],
              })
            })
          })
        }
        multiple ? checkbox() : radio()
      },
      {
        immediate: true,
      }
    )

    //模板值改变时
    watch(tempValue, (newValue) => {
      setTimeout(() => {
        columnCollapseTags && selfAdaption()
      }, 200)
      emit('update:modelValue', tempValue.value)
    })

    //自定义转化
    watch(
      [convertSetting, data],
      ([newCs, newData]) => {
        Object.assign(tempConvertSetting.value, newCs)
        const { convert } = tempConvertSetting.value
        if (convert) {
          const data = JSON.parse(JSON.stringify(newData)) //和v2不一样，v3是浅拷贝
          tempData.value = convertToTree(data)
          // emit('update:data', tempData.value)
        } else {
          tempData.value = newData
          // emit('update:data', tempData.value)
        }
      },
      { immediate: true }
    )

    return {
      tempValue,
      checkString,
      clear,
      visibleChange,
      removeTag,
      random,
      eveDropDownTreeSelect,
      filterMethod,
      label,
      tempData,
      filterNodeMethod,
      showCheckbox,
      defaultExpandedKeys,
      check,
      nodeClick,
      setCurrentKey,
      tree,
      getCurrentNode,
      convertToTree,
      option,
      number,
      numberLeft,
      spacing,
    }
  },
})
</script>


<style lang="scss" >
@import './drop-down-tree.scss';
</style>

